package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"os"
	"time"
)

func main() {
	// 生成CA证书
	caCert, caKey := generateCertificate(true, nil, nil, "CA", "EMS CA", 365*10)
	saveCertAndKey("ca.crt", "ca.key", caCert, caKey)

	// 生成服务端证书
	serverCert, serverKey := generateCertificate(false, caCert, caKey, "Server", "EMS Server", 365)
	saveCertAndKey("server.crt", "server.key", serverCert, serverKey)

	// 生成客户端证书
	clientCert, clientKey := generateCertificate(false, caCert, caKey, "Client", "EMS Client", 365)
	saveCertAndKey("client.crt", "client.key", clientCert, clientKey)
}

func generateCertificate(isCA bool, parentCert *x509.Certificate, parentKey *rsa.PrivateKey, commonName, org string, daysValid int) (*x509.Certificate, *rsa.PrivateKey) {
	priv, _ := rsa.GenerateKey(rand.Reader, 4096)
	serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			CommonName:   commonName,
			Organization: []string{org},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(time.Hour * 24 * time.Duration(daysValid)),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA:                  isCA,
	}

	if isCA {
		template.KeyUsage |= x509.KeyUsageCertSign
	}

	var certBytes []byte
	var err error
	if isCA {
		certBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	} else {
		certBytes, err = x509.CreateCertificate(rand.Reader, &template, parentCert, &priv.PublicKey, parentKey)
	}

	if err != nil {
		panic(err)
	}

	cert, _ := x509.ParseCertificate(certBytes)
	return cert, priv
}

func saveCertAndKey(certFile, keyFile string, cert *x509.Certificate, key *rsa.PrivateKey) {
	// 保存证书
	certOut, _ := os.Create(certFile)
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
	certOut.Close()

	// 保存私钥
	keyOut, _ := os.Create(keyFile)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	keyOut.Close()
}
